#include "lua-api.h"
#include "lauxlib.h"
#include "lua-utils.h"
#include "lua.h"

using namespace std;

int LuaApi::take_taxi(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckType(lptr, LUA_TSTRING, 1);
    string dest = lua_tostring(lptr, 1);
    api_->take_taxi(dest);
  } while (false);
  return 0;
}

int LuaApi::destroy(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    uint8 bag_index = lua_tointeger(lptr, 1);
    uint8 slot = lua_tointeger(lptr, 2);
    game_->destroy(bag_index, slot);
    Sleep(200);
  } while (false);
  return 0;
}

int LuaApi::sell(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 4);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    CheckInteger(lptr, 3);
    CheckInteger(lptr, 4);
    uint64 npc_id1 = lua_tointeger(lptr, 1);
    uint64 npc_id2 = lua_tointeger(lptr, 2);
    uint64 item_id1 = lua_tointeger(lptr, 3);
    uint64 item_id2 = lua_tointeger(lptr, 4);
    game_->sell(npc_id1, npc_id2, item_id1, item_id2);
    Sleep(200);
  } while (false);
  return 0;
}

int LuaApi::equip(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 3);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    CheckInteger(lptr, 3);
    uint8 bag_index = lua_tointeger(lptr, 1);
    uint8 container_slot = lua_tointeger(lptr, 2);
    uint8 equip_slot = lua_tointeger(lptr, 3);
    game_->equip(bag_index, container_slot, equip_slot);
    Sleep(200);
  } while (false);
  return 0;
}

int LuaApi::send_mail(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 6);
    const uint64 mailbox = lua_tointeger(lptr, 1);
    const std::string receiver = lua_tostring(lptr, 2);
    const std::string subject = lua_tostring(lptr, 3);
    const std::string content = lua_tostring(lptr, 4);
    const uint32 gold = lua_tointeger(lptr, 5);
    std::vector<uint64> items;
    uint32 num = (uint32)lua_rawlen(lptr, -1);
    if (num >= 1) {
      for (uint32 i = 1; i <= num; ++i) {
        lua_rawgeti(lptr, -1, i);
        const uint64 item = lua_tointeger(lptr, -1);
        items.push_back(item);
        lua_pop(lptr, 1);
      }
    }
    lua_pop(lptr, 6);
    game_->send_mail(mailbox, receiver, subject, content, items, gold);
  } while (false);
  return 0;
}

// 捡尸体/钓鱼/开门/采矿/挖草药/开宝箱
int LuaApi::loot(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    uint64 id1 = lua_tointeger(lptr, 1);
    uint64 id2 = lua_tointeger(lptr, 2);
    game_->select(id1, id2, 4);
    Sleep(200);
  } while (false);
  return 0;
}

// 左键选择 unit
int LuaApi::select(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    uint64 id1 = lua_tointeger(lptr, 1);
    uint64 id2 = lua_tointeger(lptr, 2);
    game_->select(id1, id2);
    Sleep(200);
  } while (false);
  return 0;
}

// 这个主要是用来释放技能的, 比如暴风雪, 扰乱需要选择坐标点击
int LuaApi::click(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 3);
    CheckType(lptr, LUA_TNUMBER, 1);
    CheckType(lptr, LUA_TNUMBER, 2);
    CheckType(lptr, LUA_TNUMBER, 3);
    float x = lua_tonumber(lptr, 1);
    float y = lua_tonumber(lptr, 2);
    float z = lua_tonumber(lptr, 3);
    game_->click(x, y, z);
    Sleep(200);
  } while (false);
  return 0;
}

int LuaApi::macro(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckType(lptr, LUA_TSTRING, 1);
    string content = lua_tostring(lptr, 1);
    game_->macro(content);
  } while (false);
  return 0;
}

int LuaApi::go(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 3);
    CheckType(lptr, LUA_TNUMBER, 1);
    CheckType(lptr, LUA_TNUMBER, 2);
    CheckType(lptr, LUA_TNUMBER, 3);
    float x = lua_tonumber(lptr, 1);
    float y = lua_tonumber(lptr, 2);
    float z = lua_tonumber(lptr, 3);
    api_->go(x, y, z);
  } while (false);
  return 0;
}

int LuaApi::grounds(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    auto grounds = api_->grounds();
    lua_newtable(lptr);
    int i = 1;
    for (auto ground : grounds) {
      lua_pushinteger(lptr, i);
      lua_newtable(lptr);
      pushkv(lptr, "ptr", ground->ptr());
      pushkv(lptr, "id1", ground->id1());
      pushkv(lptr, "id2", ground->id2());
      pushkv(lptr, "name", ground->name());
      pushkv(lptr, "x", ground->x());
      pushkv(lptr, "y", ground->y());
      pushkv(lptr, "z", ground->z());
      pushkv(lptr, "ownerId1", ground->owner_id1());
      pushkv(lptr, "ownerId2", ground->owner_id2());
      // pushkv(lptr, "dist", game_->player()->dist_ground(ground));
      pushkf(lptr, "dist",
             [=, this]() { return api_->player()->dist_ground(ground); });
      pushkf(lptr, "baitState", [=]() { return ground->bait_state(); });
      lua_settable(lptr, -3);
      i++;
    }
    return 1;
  } while (false);
  return 0;
}

// bagId [0, 4], 0 是主背包, 1-4 是背包
// slot [0, get_container_num_slots(bagId))
// 用 UseContainerItem(bagId, slot) 时, slot 要 +1
// wow 的这个 api 的 slot 从 1 开始
int LuaApi::get_container_item(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    uint32 bag_id = lua_tointeger(lptr, 1);
    uint32 slot = lua_tointeger(lptr, 2);
    auto itemopt = api_->get_container_item(bag_id, slot);
    if (itemopt.has_value()) {
      auto item = itemopt.value();
      lua_newtable(lptr);
      pushkv(lptr, "ptr", item.ptr());
      pushkv(lptr, "id1", item.id1());
      pushkv(lptr, "id2", item.id2());
      pushkv(lptr, "name", item.name());
      pushkv(lptr, "num", item.num());
      pushkv(lptr, "rarity", item.rarity());
      pushkv(lptr, "minLevel", item.min_level());
      pushkv(lptr, "level", item.level());
      pushkv(lptr, "price", item.price());
      pushkv(lptr, "typeIndex", item.type_index());
      pushkv(lptr, "type", item.type_name());
      pushkv(lptr, "subtypeIndex", item.subtype_index());
      pushkv(lptr, "subtype", item.subtype_name());
      pushkv(lptr, "isBound", item.is_bound());
      return 1;
    }
  } while (false);
  return 0;
}

int LuaApi::get_container_item_by_id(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    uint64 id1 = lua_tointeger(lptr, 1);
    uint64 id2 = lua_tointeger(lptr, 2);
    auto itemopt = api_->get_container_item_by_id(id1, id2);
    if (itemopt.has_value()) {
      auto item = itemopt.value();
      lua_newtable(lptr);
      pushkv(lptr, "ptr", item.ptr());
      pushkv(lptr, "id1", item.id1());
      pushkv(lptr, "id2", item.id2());
      pushkv(lptr, "name", item.name());
      pushkv(lptr, "num", item.num());
      pushkv(lptr, "rarity", item.rarity());
      pushkv(lptr, "minLevel", item.min_level());
      pushkv(lptr, "level", item.level());
      pushkv(lptr, "price", item.price());
      pushkv(lptr, "typeIndex", item.type_index());
      pushkv(lptr, "type", item.type_name());
      pushkv(lptr, "subtypeIndex", item.subtype_index());
      pushkv(lptr, "subtype", item.subtype_name());
      return 1;
    }
  } while (false);
  return 0;
}

int LuaApi::get_num_free_slots(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    auto num_free_slots = api_->get_num_free_slots();
    lua_pushinteger(lptr, num_free_slots);
  } while (false);
  return 1;
}

// bag_id: 0-4, 0 是主背包, 1-4 是背包
// 如果返回 0 表示那个位置没有背包
int LuaApi::get_container_num_slots(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckInteger(lptr, 1);
    auto bag_id = lua_tointeger(lptr, 1);
    auto num_slots = api_->get_container_num_slots(bag_id);
    lua_pushinteger(lptr, num_slots);
  } while (false);
  return 1;
}

int LuaApi::get_unit_by_id(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckInteger(lptr, 2);
    uint64 id1 = lua_tointeger(lptr, 1);
    uint64 id2 = lua_tointeger(lptr, 2);
    auto unit = api_->get_unit(id1, id2);
    if (unit.has_value()) {
      lua_newtable(lptr);
      pushkv(lptr, "ptr", unit->ptr());
      pushkv(lptr, "id1", unit->id1());
      pushkv(lptr, "id2", unit->id2());
      pushkv(lptr, "name", unit->name());
      pushkv(lptr, "level", unit->level());
      pushkv(lptr, "type", unit->creature_type());
      pushkf(lptr, "tid1", [unit]() { return unit->tid1(); });
      pushkf(lptr, "tid2", [unit]() { return unit->tid2(); });
      pushkf(lptr, "hp", [unit]() { return unit->hp(); });
      pushkf(lptr, "hpMax", [unit]() { return unit->hp_max(); });
      pushkf(lptr, "hpRate", [unit]() { return unit->hp() / unit->hp_max(); });
      pushkf(lptr, "mp", [unit]() { return unit->mp(); });
      pushkf(lptr, "mpMax", [unit]() { return unit->mp_max(); });
      pushkf(lptr, "mpRate", [unit]() { return unit->mp() / unit->mp_max(); });
      pushkf(lptr, "x", [unit]() { return unit->x(); });
      pushkf(lptr, "y", [unit]() { return unit->y(); });
      pushkf(lptr, "z", [unit]() { return unit->z(); });
      pushkf(lptr, "dist", [unit, this]() {
        return api_->player()->dist_unit(unit->role());
      });
      pushkf(lptr, "casting",
             [unit]() { return unit->casting() > 0 || unit->channel() > 0; });
      pushkf_str(lptr, "hasBuff", [unit, this](const std::string &buff_name) {
        return api_->unit_has_buff(unit->ptr(), buff_name);
      });
      pushkf(lptr, "dead", [unit]() { return unit->hp() == 0; });
      pushkf(lptr, "lootable", [unit]() { return unit->lootable(); });
      pushkf(lptr, "combating", [unit]() { return unit->combating(); });
      return 1;
    }
  } while (false);
  return 0;
}

int LuaApi::get_spell_cooldown(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckInteger(lptr, 1);
    uint32 spell_id = lua_tointeger(lptr, 1);
    auto [start, duration, enable] = game_->get_spell_cooldown(spell_id);
    lua_pushinteger(lptr, start);
    lua_pushinteger(lptr, duration);
    lua_pushinteger(lptr, enable);
    return 3;
  } while (false);
  return 0;
}

int LuaApi::spells(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    auto spells = api_->spells();
    lua_newtable(lptr);
    for (auto spell : spells) {
      pushkv(lptr, spell.first.c_str(), spell.second.spell_id);
    }
    return 1;
  } while (false);
  return 0;
}

int LuaApi::get_buff(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckType(lptr, LUA_TSTRING, 1);
    string name = lua_tostring(lptr, 1);
    auto buffopt = api_->get_buff(name);
    if (buffopt.has_value()) {
      auto buff = buffopt.value();
      lua_newtable(lptr);
      pushkv(lptr, "buffId", buff.buff_id);
      pushkv(lptr, "name", buff.name);
      pushkv(lptr, "duration", buff.duration);
      pushkv(lptr, "expiration", buff.expiration);
      return 1;
    }
  } while (false);
  return 0;
}
int LuaApi::unit_get_buff(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 2);
    CheckInteger(lptr, 1);
    CheckType(lptr, LUA_TSTRING, 2);
    uint64 unit_ptr = lua_tointeger(lptr, 1);
    string name = lua_tostring(lptr, 2);
    auto buffopt = api_->unit_get_buff(unit_ptr, name);
    if (buffopt.has_value()) {
      auto buff = buffopt.value();
      lua_newtable(lptr);
      pushkv(lptr, "buffId", buff.buff_id);
      pushkv(lptr, "name", buff.name);
      pushkv(lptr, "duration", buff.duration);
      pushkv(lptr, "expiration", buff.expiration);
      return 1;
    }
  } while (false);
  return 0;
}

int LuaApi::units(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    auto units = api_->units();
    lua_newtable(lptr);
    int i = 1;
    for (auto unit : units) {
      lua_pushinteger(lptr, i);
      lua_newtable(lptr);
      pushkv(lptr, "ptr", unit->ptr());
      pushkv(lptr, "id1", unit->id1());
      pushkv(lptr, "id2", unit->id2());
      pushkv(lptr, "name", unit->name());
      pushkv(lptr, "level", unit->level());
      pushkv(lptr, "type", unit->creature_type());
      pushkf(lptr, "tid1", [unit]() { return unit->tid1(); });
      pushkf(lptr, "tid2", [unit]() { return unit->tid2(); });
      pushkf(lptr, "hp", [unit]() { return unit->hp(); });
      pushkf(lptr, "hpMax", [unit]() { return unit->hp_max(); });
      pushkf(lptr, "hpRate", [unit]() { return unit->hp() / unit->hp_max(); });
      pushkf(lptr, "mp", [unit]() { return unit->mp(); });
      pushkf(lptr, "mpMax", [unit]() { return unit->mp_max(); });
      pushkf(lptr, "mpRate", [unit]() { return unit->mp() / unit->mp_max(); });
      pushkf(lptr, "x", [unit]() { return unit->x(); });
      pushkf(lptr, "y", [unit]() { return unit->y(); });
      pushkf(lptr, "z", [unit]() { return unit->z(); });
      pushkf(lptr, "dist", [unit, this]() {
        return api_->player()->dist_unit(unit->role());
      });
      pushkf(lptr, "casting",
             [unit]() { return unit->casting() > 0 || unit->channel() > 0; });
      pushkf_str(lptr, "hasBuff", [unit, this](const std::string &buff_name) {
        return api_->unit_has_buff(unit->ptr(), buff_name);
      });
      pushkf(lptr, "dead", [unit]() { return unit->hp() == 0; });
      pushkf(lptr, "lootable", [unit]() { return unit->lootable(); });
      pushkf(lptr, "combating", [unit]() { return unit->combating(); });
      lua_settable(lptr, -3);
      i++;
    }
    return 1;
  } while (false);
  return 0;
}

int LuaApi::player(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    auto player = api_->player();
    if (player) {
      lua_newtable(lptr);
      pushkv(lptr, "ptr", player->ptr());
      pushkv(lptr, "id1", player->id1());
      pushkv(lptr, "id2", player->id2());
      pushkv(lptr, "name", player->name());
      pushkf(lptr, "tid1", [player]() { return player->tid1(); });
      pushkf(lptr, "tid2", [player]() { return player->tid2(); });
      pushkf(lptr, "level", [player]() { return player->level(); });
      pushkf(lptr, "hp", [player]() { return player->hp(); });
      pushkf(lptr, "hpMax", [player]() { return player->hp_max(); });
      pushkf(lptr, "hpRate",
             [player]() { return player->hp() / player->hp_max(); });
      pushkf(lptr, "mp", [player]() { return player->mp(); });
      pushkf(lptr, "mpMax", [player]() { return player->mp_max(); });
      pushkf(lptr, "mpRate",
             [player]() { return player->mp() / player->mp_max(); });
      pushkf(lptr, "x", [player]() { return player->x(); });
      pushkf(lptr, "y", [player]() { return player->y(); });
      pushkf(lptr, "z", [player]() { return player->z(); });
      pushkf(lptr, "casting", [player]() {
        return player->casting() > 0 || player->channel() > 0;
      });
      pushkf_str(lptr, "hasBuff", [this](const std::string &buff_name) {
        return api_->has_buff(buff_name);
      });
      pushkf(lptr, "dead", [player]() { return player->hp() == 0; });
      pushkf(lptr, "combating", [player]() { return player->combating(); });
      pushkf(lptr, "onTaxi", [player]() { return player->on_taxi(); });
      return 1;
    } else {
      dbgprintu8("codergege [%s][%03d] player 空指针\n", __FUNCTION__,
                 __LINE__);
    }
  } while (false);
  return 0;
}

// 这个获取的是设置的脚本运行时间
int LuaApi::get_time(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    uint32 time = api_->get_time();
    lua_pushinteger(lptr, time);
    return 1;
  } while (false);
  return 0;
}

// 这个是游戏内时间, 用于法术计时
int LuaApi::get_game_time(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 0);
    uint32 time = game_->get_time();
    lua_pushinteger(lptr, time);
    return 1;
  } while (false);
  return 0;
}

int LuaApi::key_down(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckInteger(lptr, 1);
    auto code = lua_tointeger(lptr, 1);
    game_->key_down(code);
  } while (false);
  return 0;
}

int LuaApi::key_up(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckInteger(lptr, 1);
    auto code = lua_tointeger(lptr, 1);
    game_->key_up(code);
  } while (false);
  return 0;
}

// Usage: Sleep(time_in_milliseconds)
int LuaApi::sleep(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTop(lptr, 1);
    CheckInteger(lptr, 1);
    auto time = lua_tointeger(lptr, 1);
    // Sleep(time);
    // WaitStopBreak(time);
    WaitSleep(time);
  } while (false);
  return 0;
}

// Usage: Printf(format_string, arg1, arg2,...)
int LuaApi::printf(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTopGE(lptr, 2);
    CheckType(lptr, LUA_TSTRING, 1);
    const char *format = lua_tostring(lptr, 1);
    if (!format) {
      dbgprintu8("First argument must be a format string.\n");
      return 0;
    }
    CHAR buf[4096]{0};
    int buf_pos = 0;
    int arg_pos =
        2; // Start processing arguments from the second position on the stack
    while (*format != '\0') {
      if (*format == '%') {
        format++;
        switch (*format) {
        case 'd': // Integer
        case 'i':
          if (lua_isinteger(lptr, arg_pos)) {
            lua_Integer value = lua_tointeger(lptr, arg_pos);
            buf_pos += sprintf(buf + buf_pos, "%lld", (long long)value);
          }
          arg_pos++;
          break;
        case 'x': // llX 显示
        case 'X': // llX 显示
          if (lua_isinteger(lptr, arg_pos)) {
            lua_Integer value = lua_tointeger(lptr, arg_pos);
            buf_pos += sprintf(buf + buf_pos, "0x%llX", (uint64)value);
          }
          arg_pos++;
          break;
        case 'f': // Float
          if (lua_isnumber(lptr, arg_pos)) {
            lua_Number value = lua_tonumber(lptr, arg_pos);
            buf_pos += sprintf(buf + buf_pos, "%.2f", value);
          }
          arg_pos++;
          break;
        case 's': // String
          if (lua_isstring(lptr, arg_pos)) {
            const char *str = lua_tostring(lptr, arg_pos);
            buf_pos += sprintf(buf + buf_pos, "%s", str);
          }
          arg_pos++;
          break;
        case 'b': // String
          if (lua_isboolean(lptr, arg_pos)) {
            boolean ret = lua_toboolean(lptr, arg_pos);
            buf_pos += sprintf(buf + buf_pos, "%s", ret ? "true" : "false");
          }
          arg_pos++;
          break;
        // Add more cases for other format specifiers as needed
        default:
          // Unknown format specifier, just copy it as-is
          buf[buf_pos++] = '%';
          buf[buf_pos++] = *format;
          break;
        }
        format++;
      } else {
        // Copy regular characters as-is
        buf[buf_pos++] = *format++;
      }
    }
    buf[buf_pos] = '\0'; // Null-terminate the string
    dbgprintu8("codergege %s\n", buf);
  } while (false);
  return 0;
}

// Usage: Print(arg1, arg2, arg3,...)
int LuaApi::print(LuaPtr lptr) {
  do {
    WaitStopBreak(1);
    CheckTopGE(lptr, 1);
    int top = lua_gettop(lptr);
    CHAR buf[1024]{0};
    std::string outputString = "codergege ";
    for (int i = 1; i <= top; i++) {
      WaitStopBreak(1);
      if (lua_isinteger(lptr, i)) {
        auto num = lua_tointeger(lptr, i);
        snprintf(buf, 1024, "%lld(0x%llX) ", num, num);
      } else if (lua_isnumber(lptr, i)) {
        double num = (double)lua_tonumber(lptr, i);
        QWORD qnum = *(QWORD *)&num;
        snprintf(buf, 1024, "%f(0x%llX) ", num, qnum);
      } else if (lua_isstring(lptr, i)) {
        std::string content = lua_tostring(lptr, i);
        // std::string asciiContent = u8toa(content);
        snprintf(buf, 1024, "%s ", content.c_str());
      } else if (lua_isboolean(lptr, i)) {
        bool boolRet = lua_toboolean(lptr, i);
        snprintf(buf, 1024, "%s ", boolRet ? "true" : "false");
      }
      outputString += buf;
    }
    outputString += "\n";
    dbgprintu8(outputString.c_str());
  } while (false);
  return 0;
}

LuaApi::LuaApi(shared_ptr<Game> game, shared_ptr<Api> api)
    : game_(game), api_(api) {}

LuaApi::~LuaApi() {}
